<template>
  <div>
    <div style="position: relative">
      <video width="640" height="480" ref="video">
        您的浏览器不支持 video 标签。
      </video>
      <canvas
        id="overlay"
        width="640"
        height="480"
        style="position: absolute; top: 0; left: 0"
      />
    </div>

    <div class="t-a-c" style="width: 640px">
      {{ video.error }}

      <br />

      <el-space>
        <template v-if="!video.stream">
          <el-upload
            ref="uploadRef"
            accept=".mp4"
            :auto-upload="false"
            :show-file-list="false"
            :on-change="uploadChangeHandler"
          >
            <template #trigger>
              <el-button type="primary">上传视频</el-button>
            </template>
          </el-upload>

          <el-button type="primary" @click.native="openCamera"
            >打开摄像头</el-button
          >
          <el-button type="primary" @Click="shareDesktop">分享桌面</el-button>
        </template>

        <template v-if="video.stream">
          <el-button
            v-if="!webrtc.connection"
            type="primary"
            @click="initWebrtc"
          >
            打开Webrtc
          </el-button>
          <el-button
            v-else
            type="primary"
            :loading="webrtc.loading"
            @click="close"
          >
            关闭
          </el-button>
        </template>
      </el-space>
    </div>
  </div>
</template>
<script>
import { onIceCandidate } from '@/api/webrtc'

export default {
  name: 'Index',
  data() {
    return {
      video: {
        scale: 1,
        offsetLeft: 0,
        offsetTop: 0,
        ctx: null,
        stream: null,
        error: null,
      },
      webrtc: {
        loading: false,
        cname: null,
        connection: null,
        connectedCallback: null,
        configuration: {
          iceServers: [],
        },
      },
    }
  },
  mounted() {
    const overlay = document.getElementById('overlay')
    this.video.ctx = overlay.getContext('2d')
  },
  methods: {
    uploadChangeHandler(file) {
      this.$refs.video.src = URL.createObjectURL(file.raw)

      this.video.stream = this.$refs.video.captureStream()
      this.video.error = null

      setTimeout(() => {
        this.webrtc.connectedCallback = () => {
          this.$refs.video.play()
          this.calculateVideoOffsets()
        }

        this.initWebrtc()
      }, 100)
    },
    openCamera() {
      navigator.getUserMedia =
        navigator.getUserMedia ||
        navigator.webkitGetUserMedia ||
        navigator.mozGetUserMedia

      if (!navigator.getUserMedia) {
        this.video.error = '没有检测到摄像头'
        this.$message.error(this.video.error)
        return
      }

      navigator.getUserMedia(
        { video: true, audio: false },
        (stream) => {
          this.video.stream = stream
          this.video.error = null

          this.$refs.video.srcObject = stream
          this.$refs.video.play()
          this.initWebrtc()
        },
        (e) => {
          switch (e.name) {
            case 'NotReadableError':
              this.video.error = '摄像头不可用，可能被其他应用所占用'
              break
            case 'NotAllowedError':
              this.video.error = '摄像头被禁用'
              break
            case 'NotFoundError':
              this.video.error = '没有检测到摄像头'
              break
            default: {
              this.video.error = '摄像头未知错误'
            }
          }
          this.$message.error(this.video.error)
        }
      )
    },

    shareDesktop() {
      const displayMediaOptions = {
        video: {
          cursor: 'always', // 显示鼠标光标
        },
        audio: false,
      }

      navigator.mediaDevices
        .getDisplayMedia(displayMediaOptions)
        .then((stream) => {
          this.video.stream = stream
          this.video.error = null

          this.$refs.video.srcObject = stream
          this.$refs.video.play()
          this.initWebrtc()
        })
    },

    initWebrtc() {
      this.webrtc.loading = true
      this.webrtc.connection = new RTCPeerConnection(this.webrtc.configuration)
      this.webrtc.connection.onicecandidate = (e) => {
        if (e.candidate) {
          console.log('push onicecandidate', e.candidate)

          onIceCandidate(this.webrtc.cname, JSON.stringify(e.candidate))
        } else {
          console.log('this is the end candidate', e.candidate)
        }
      }

      this.webrtc.connection.oniceconnectionstatechange = (e) => {
        console.log('oniceconnectionstatechange', e)
      }

      this.webrtc.connection.addStream(this.video.stream)

      this.webrtc.connection
        .createOffer({
          offerToRecieveAudio: 0,
          offerToRecieveVideo: 1,
        })
        .then((offer) => {
          console.log('push createOffer', offer)
          this.webrtc.connection.setLocalDescription(offer)

          let startIndex = offer.sdp.indexOf('cname:') + 6
          let endIndex = offer.sdp.indexOf('\r', startIndex)

          this.webrtc.cname = btoa(
            offer.sdp.substr(startIndex, endIndex - startIndex)
          )
          this.sendOffer(offer)
        })
        .catch(console.error)
    },

    sendOffer(offer) {
      let eventSource = new EventSource(
        '/api/webrtc/createOffer?offer=' +
          encodeURIComponent(JSON.stringify(offer))
      )
      eventSource.onopen = () => {}
      eventSource.onmessage = (e) => {
        let res = JSON.parse(e.data)
        if (res.resultCode != 200) {
          eventSource.onerror()
          window.speechSynthesis.speak(
            new SpeechSynthesisUtterance(res.message)
          )
          return
        }

        let type = res.data.type
        let data = res.data.data
        switch (type) {
          case 'answer': {
            console.log('receive answer : ' + data)
            this.webrtc.connection.setRemoteDescription(
              new RTCSessionDescription({
                sdp: data,
                type: 'answer',
              })
            )
            this.webrtc.loading = false
            if (this.webrtc.connectedCallback) this.webrtc.connectedCallback()
            break
          }
          case 'icecandidate': {
            console.log('receive icecandidate : ' + data)
            this.webrtc.connection.addIceCandidate(
              new RTCIceCandidate({
                candidate: data.sdp,
                sdpMLineIndex: data.sdpMLineIndex,
                sdpMid: data.sdpMid,
              })
            )
            break
          }
          case 'MULTI_OBJECTS_FOUND': {
            const classNames = data
              .map((item) => {
                return item.class_name
              })
              .join(',')
            this.video.error = '检测到多目标：' + classNames
            this.drawBox(data)
            break
          }
          case 'NONE_OBJECT_FOUND': {
            this.video.error = '未检测到目标'
            this.clearBox()
            break
          }
          case 'OBJECT_FOUND': {
            this.video.error =
              '检测到目标：#' + data.tracker_id + ' ' + data.class_name
            this.drawBox([data])
            break
          }
          case 'OBJECT_CHANGE': {
            this.video.error =
              '目标更换：#' + data.tracker_id + ' ' + data.class_name
            this.drawBox([data])
            break
          }
          case 'OBJECT_MOVE': {
            this.video.error =
              '目标移动：#' + data.tracker_id + ' ' + data.class_name
            this.drawBox([data])
            break
          }
          case 'OBJECT_FIXED': {
            this.video.error =
              '目标固定：#' + data.tracker_id + ' ' + data.class_name
            this.drawBox([data])
            break
          }
          case 'OBJECT_STABLE': {
            this.video.error =
              '目标平稳：#' + data.tracker_id + ' ' + data.class_name
            this.drawBox([data])
            break
          }
        }
      }
      eventSource.onerror = () => {
        eventSource.close()
      }
    },

    close() {
      this.webrtc.connection.close()
      this.webrtc.connection = null
      this.webrtc.connectedCallback = null
      this.webrtc.cname = null

      this.video.stream.getTracks().forEach((track) => track.stop())
      this.video.stream = null
      this.video.error = null
      this.video.target = null
      this.video.offsetLeft = 0
      this.video.offsetTop = 0

      this.$refs.video.srcObject = null
      const url = this.$refs.video.src
      if (url) {
        URL.revokeObjectURL(url)
        this.$refs.video.src = null
      }

      const overlay = document.getElementById('overlay')
      const ctx = this.video.ctx
      ctx.clearRect(0, 0, overlay.width, overlay.height) // 清除之前的绘制
    },

    calculateVideoOffsets() {
      const videoElement = this.$refs.video

      // 获取视频的自然尺寸
      const naturalWidth = videoElement.videoWidth
      const naturalHeight = videoElement.videoHeight

      // 获取容器尺寸
      const containerWidth = videoElement.offsetWidth
      const containerHeight = videoElement.offsetHeight

      if (naturalWidth / naturalHeight > containerWidth / containerHeight) {
        // 如果视频宽高比大于容器宽高比，上下有黑边
        const scaledHeight = (containerWidth / naturalWidth) * naturalHeight
        this.video.offsetTop = (containerHeight - scaledHeight) / 2
        this.video.scale = containerWidth / naturalWidth
      } else {
        // 如果视频宽高比小于容器宽高比，左右有黑边
        const scaledWidth = (containerHeight / naturalHeight) * naturalWidth
        this.video.offsetLeft = (containerWidth - scaledWidth) / 2
        this.video.scale = containerHeight / naturalHeight
      }
    },

    clearBox() {
      const overlay = document.getElementById('overlay')
      const ctx = this.video.ctx
      ctx.clearRect(0, 0, overlay.width, overlay.height) // 清除之前的绘制
    },

    drawBox(data) {
      this.clearBox()

      const videoElement = this.$refs.video
      // 获取视频的自然尺寸
      const naturalWidth = videoElement.videoWidth
      const naturalHeight = videoElement.videoHeight

      const scale = this.video.scale
      const ctx = this.video.ctx
      data.forEach((object) => {
        ctx.strokeStyle = 'green'
        ctx.lineWidth = 2
        ctx.strokeRect(
          scale * naturalWidth * object.xyxy[0] + this.video.offsetLeft,
          scale * naturalHeight * object.xyxy[1] + this.video.offsetTop,
          scale * naturalWidth * (object.xyxy[2] - object.xyxy[0]),
          scale * naturalHeight * (object.xyxy[3] - object.xyxy[1])
        )

        // 绘制标签
        ctx.font = '16px Arial'
        ctx.fillStyle = 'green'
        ctx.fillText(
          '#' + object.tracker_id + ' ' + object.class_name,
          scale * naturalWidth * object.xyxy[0] + this.video.offsetLeft,
          scale * naturalHeight * object.xyxy[1] + this.video.offsetTop - 5
        )
      })
    },

    xyxyChange(xyxy1, xyxy2) {
      for (let i = 0; i < 4; i++) {
        let diff = Math.abs(xyxy1[i] - xyxy2[i])
        if (diff > 5) {
          return true
        }
      }
      return false
    },
  },
}
</script>
